package org.dromara.common.tenant.config;

import cn.dev33.satoken.dao.SaTokenDao;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import org.dromara.common.core.utils.reflect.ReflectUtils;
import org.dromara.common.mybatis.config.MybatisPlusConfig;
import org.dromara.common.redis.config.RedisConfig;
import org.dromara.common.redis.config.properties.RedissonProperties;
import org.dromara.common.tenant.core.TenantSaTokenDao;
import org.dromara.common.tenant.handle.PlusTenantLineHandler;
import org.dromara.common.tenant.handle.TenantKeyPrefixHandler;
import org.dromara.common.tenant.manager.TenantSpringCacheManager;
import org.dromara.common.tenant.properties.TenantProperties;
import org.redisson.config.ClusterServersConfig;
import org.redisson.config.SingleServerConfig;
import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;


/**
 * @Author 刘武贵
 * @Date 2024/8/15 22:00
 * @Description 功能描述：租户配置类
 */
// 启用TenantProperties配置属性
@EnableConfigurationProperties(TenantProperties.class)
// 指定该配置依赖于RedisConfig配置，确保Redis配置完成后才加载该配置
@AutoConfiguration(after = {RedisConfig.class})
// 当配置文件中的"tenant.enable"属性值为"true"时，条件加载该配置
@ConditionalOnProperty(value = "tenant.enable", havingValue = "true")
public class TenantConfig {

    @ConditionalOnBean(MybatisPlusConfig.class)
    @AutoConfiguration(after = {MybatisPlusConfig.class})
    static class MybatisPlusConfiguration {

        /**
         * 多租户插件
         * 配置租户线路内部拦截器
         * 该方法通过@Bean注解指示该方法返回的实例将被Spring容器管理，自动注入到需要的地方
         * 拦截器用于在操作执行前进行租户线路的相关处理，确保每条SQL语句都带有正确的租户线路信息
         *
         * @param tenantProperties 租户属性配置，用于设置租户线路的相关参数
         * @return 返回配置好的TenantLineInnerInterceptor实例，用于在应用执行过程中自动处理租户线路
         */
        @Bean
        public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantProperties tenantProperties) {
            return new TenantLineInnerInterceptor(new PlusTenantLineHandler(tenantProperties));
        }

    }

    /**
     * 配置Redisson的多租户支持
     * 该方法通过接收Redisson的属性配置，来定制化初始化Redisson客户端
     * 主要功能是为Redisson配置不同的服务器模式下的多租户支持，如单机模式和集群模式
     *
     * @param redissonProperties Redisson的属性配置，包含连接信息、配置细节等
     * @return 一个RedissonAutoConfigurationCustomizer实例，用于在Redisson初始化时进行定制化配置
     */
    @Bean
    public RedissonAutoConfigurationCustomizer tenantRedissonCustomizer(RedissonProperties redissonProperties) {
        return config -> {
            // 创建一个TenantKeyPrefixHandler实例，用于处理多租户的Redis键前缀
            TenantKeyPrefixHandler nameMapper = new TenantKeyPrefixHandler(redissonProperties.getKeyPrefix());
            // 获取单机模式配置
            SingleServerConfig singleServerConfig = ReflectUtils.invokeGetter(config, "singleServerConfig");
            if (ObjectUtil.isNotNull(singleServerConfig)) {
                // 使用单机模式
                // 设置多租户 redis key前缀
                singleServerConfig.setNameMapper(nameMapper);
                ReflectUtils.invokeSetter(config, "singleServerConfig", singleServerConfig);
            }
            // 获取集群模式配置
            ClusterServersConfig clusterServersConfig = ReflectUtils.invokeGetter(config, "clusterServersConfig");
            // 集群配置方式 参考下方注释
            if (ObjectUtil.isNotNull(clusterServersConfig)) {
                // 设置多租户 redis key前缀
                clusterServersConfig.setNameMapper(nameMapper);
                ReflectUtils.invokeSetter(config, "clusterServersConfig", clusterServersConfig);
            }
        };
    }

    /**
     * 多租户缓存管理器
     * 配置租户缓存管理器
     * 通过@Primary注解指定该Bean为首选，解决相同类型多个Bean的冲突问题
     * 使用@Bean注解将该方法纳入Spring的管理，使其成为一个Bean定义
     *
     * @return 返回一个租户专用的缓存管理器实例，用于管理和操作租户级别的缓存
     */
    @Primary
    @Bean
    public CacheManager tenantCacheManager() {
        return new TenantSpringCacheManager();
    }

    /**
     * 多租户鉴权dao实现
     * 配置租户SaTokenDao实现
     * 通过@Primary注解指定此Bean为首选Bean，解决相同类型Bean的重复注入问题
     * 使用@Bean注解声明一个Bean对象，使其可以被Spring容器管理和注入到其他Bean中
     *
     * @return 返回一个TenantSaTokenDao实例，用于处理租户的Token数据访问
     */
    @Primary
    @Bean
    public SaTokenDao tenantSaTokenDao() {
        return new TenantSaTokenDao();
    }

}
